From bf03c85e432d6c2439338e2bbb1fbc08843635f1 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 10 Mar 2013 01:11:18 -0500 Subject: [PATCH] window: Allow resizing by clicking on the border We also change cursors to indicate what interactions are possible in various window regions. Double clicking on the titlebar maximizes the window. --- gtk/gtkwindow.c | 400 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 376 insertions(+), 24 deletions(-) diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index ade610d221..ac81666c2b 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -147,6 +147,8 @@ struct _GtkWindowPrivate GtkWidget *title_max_button; GtkWidget *title_close_button; + GdkCursor *default_cursor; + /* The following flags are initially TRUE (before a window is mapped). * They cause us to compute a configure request that involves * default-only parameters. Once mapped, we set them to FALSE. @@ -256,6 +258,22 @@ enum { LAST_ARG }; +/* Must be kept in sync with GdkWindowEdge ! */ +typedef enum +{ + GTK_WINDOW_REGION_EDGE_NW, + GTK_WINDOW_REGION_EDGE_N, + GTK_WINDOW_REGION_EDGE_NE, + GTK_WINDOW_REGION_EDGE_W, + GTK_WINDOW_REGION_EDGE_E, + GTK_WINDOW_REGION_EDGE_SW, + GTK_WINDOW_REGION_EDGE_S, + GTK_WINDOW_REGION_EDGE_SE, + GTK_WINDOW_REGION_CONTENT, + GTK_WINDOW_REGION_TITLE, + GTK_WINDOW_REGION_EDGE +} GtkWindowRegion; + typedef struct { GList *icon_list; @@ -355,6 +373,8 @@ static gint gtk_window_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event); static gint gtk_window_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event); +static gint gtk_window_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event); static gint gtk_window_focus_in_event (GtkWidget *widget, GdkEventFocus *event); static gint gtk_window_focus_out_event (GtkWidget *widget, @@ -436,6 +456,7 @@ static void resize_grip_create_window (GtkWindow *window); static void resize_grip_destroy_window (GtkWindow *window); static void update_grip_visibility (GtkWindow *window); static void update_window_buttons (GtkWindow *window); +static void update_cursor_at_position (GtkWidget *widget, gint x, gint y); static void gtk_window_notify_keys_changed (GtkWindow *window); static GtkKeyHash *gtk_window_get_key_hash (GtkWindow *window); @@ -449,6 +470,9 @@ static void gtk_window_on_theme_variant_changed (GtkSettings *settings, #endif static void gtk_window_set_theme_variant (GtkWindow *window); +static void window_cursor_changed (GdkWindow *window, + GParamSpec *pspec, + GtkWidget *widget); static void gtk_window_get_preferred_width (GtkWidget *widget, gint *minimum_size, @@ -616,6 +640,7 @@ gtk_window_class_init (GtkWindowClass *klass) widget_class->key_release_event = gtk_window_key_release_event; widget_class->enter_notify_event = gtk_window_enter_notify_event; widget_class->leave_notify_event = gtk_window_leave_notify_event; + widget_class->motion_notify_event = gtk_window_motion_notify_event; widget_class->focus_in_event = gtk_window_focus_in_event; widget_class->button_press_event = gtk_window_button_press_event; widget_class->focus_out_event = gtk_window_focus_out_event; @@ -1032,6 +1057,13 @@ gtk_window_class_init (GtkWindowClass *klass) ":close", GTK_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("decoration-resize-handle", + P_("Decoration resize handle size"), + P_("Decoration resize handle size"), + 0, G_MAXINT, + 20, GTK_PARAM_READWRITE)); + gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("resize-grip-width", P_("Width of resize grip"), @@ -5589,6 +5621,10 @@ gtk_window_realize (GtkWidget *widget) GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK); + + if (priv->decorated && priv->client_decorated) + attributes.event_mask |= GDK_POINTER_MOTION_MASK; + attributes.type_hint = priv->type_hint; attributes_mask |= GDK_WA_VISUAL | GDK_WA_TYPE_HINT; @@ -5666,9 +5702,14 @@ gtk_window_realize (GtkWidget *widget) } #endif + /* get the default cursor */ + priv->default_cursor = gdk_window_get_cursor (gdk_window); + g_signal_connect (G_OBJECT (gdk_window), "notify::cursor", + G_CALLBACK (window_cursor_changed), widget); + /* Icons */ gtk_window_realize_icon (window); - + if (priv->has_resize_grip) resize_grip_create_window (window); } @@ -5956,10 +5997,8 @@ _gtk_window_set_allocation (GtkWindow *window, NULL, &title_height); - title_allocation.x = title_border.left + - window_border.left; - title_allocation.y = title_border.top + - window_border.top; + title_allocation.x = title_border.left + window_border.left; + title_allocation.y = title_border.top + window_border.top; title_allocation.width = MAX (1, (gint) allocation->width - title_border.left - title_border.right - @@ -6465,13 +6504,12 @@ gboolean gtk_window_propagate_key_event (GtkWindow *window, GdkEventKey *event) { - GtkWindowPrivate *priv; + GtkWindowPrivate *priv = window->priv; gboolean handled = FALSE; GtkWidget *widget, *focus; g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE); - priv = window->priv; widget = GTK_WIDGET (window); focus = priv->focus_widget; @@ -6542,11 +6580,174 @@ gtk_window_key_release_event (GtkWidget *widget, return handled; } +static GtkWindowRegion +get_region_type (GtkWindow *window, gint x, gint y) +{ + GtkWindowPrivate *priv = window->priv; + GtkWidget *widget = GTK_WIDGET (window); + gint title_height = 0; + gint resize_handle = 0; + GtkBorder window_border; + + if (priv->title_box) + title_height = gtk_widget_get_allocated_height (priv->title_box); + get_decoration_borders (widget, NULL, &window_border); + gtk_widget_style_get (widget, + "decoration-resize-handle", &resize_handle, + NULL); + + if (x < window_border.left) + { + if (y < window_border.top + MAX (title_height, resize_handle)) + return GTK_WINDOW_REGION_EDGE_NW; + else if (y > gtk_widget_get_allocated_height (widget) - window_border.bottom - resize_handle) + return GTK_WINDOW_REGION_EDGE_SW; + else + return GTK_WINDOW_REGION_EDGE_W; + } + else if (x > gtk_widget_get_allocated_width (widget) - window_border.right) + { + if (y < window_border.top + MAX (title_height, resize_handle)) + return GTK_WINDOW_REGION_EDGE_NE; + else if (y > gtk_widget_get_allocated_height (widget) - window_border.bottom - resize_handle) + return GTK_WINDOW_REGION_EDGE_SE; + else + return GTK_WINDOW_REGION_EDGE_E; + } + else if (y < window_border.top) + { + if (x < window_border.left + resize_handle) + return GTK_WINDOW_REGION_EDGE_NW; + else if (x > gtk_widget_get_allocated_width (widget) - window_border.right - resize_handle) + return GTK_WINDOW_REGION_EDGE_NE; + else + return GTK_WINDOW_REGION_EDGE_N; + } + else if (y > gtk_widget_get_allocated_height (widget) - window_border.bottom) + { + if (x < window_border.left + resize_handle) + return GTK_WINDOW_REGION_EDGE_SW; + else if (x > gtk_widget_get_allocated_width (widget) - window_border.right - resize_handle) + return GTK_WINDOW_REGION_EDGE_SE; + else + return GTK_WINDOW_REGION_EDGE_S; + } + else + { + if (y < window_border.top + title_height) + return GTK_WINDOW_REGION_TITLE; + else + return GTK_WINDOW_REGION_CONTENT; + } +} + +static GtkWindowRegion +get_active_region_type (GtkWindow *window, gint x, gint y) +{ + GtkWindowPrivate *priv = window->priv; + GtkWidget *widget = GTK_WIDGET (window); + GtkWindowRegion region; + gboolean resize_h, resize_v; + gint state; + GtkBorder window_border; + + region = get_region_type (window, x, y); + get_decoration_borders (widget, NULL, &window_border); + + state = gdk_window_get_state (gtk_widget_get_window (widget)); + if (!priv->resizable || (state & GDK_WINDOW_STATE_MAXIMIZED)) + { + resize_h = resize_v = FALSE; + } + else + { + resize_h = resize_v = TRUE; + if (priv->geometry_info) + { + GdkGeometry *geometry = &priv->geometry_info->geometry; + GdkWindowHints flags = priv->geometry_info->mask; + + if ((flags & GDK_HINT_MIN_SIZE) && (flags & GDK_HINT_MAX_SIZE)) + { + resize_h = geometry->min_width != geometry->max_width; + resize_v = geometry->min_height != geometry->max_height; + } + } + } + + switch (region) + { + case GTK_WINDOW_REGION_EDGE_N: + case GTK_WINDOW_REGION_EDGE_S: + if (resize_v) + return region; + else + return GTK_WINDOW_REGION_EDGE; + break; + + case GTK_WINDOW_REGION_EDGE_W: + case GTK_WINDOW_REGION_EDGE_E: + if (resize_h) + return region; + else + return GTK_WINDOW_REGION_EDGE; + break; + + case GTK_WINDOW_REGION_EDGE_NW: + if (resize_h && resize_v) + return region; + else if (resize_h && x < window_border.left) + return GTK_WINDOW_REGION_EDGE_W; + else if (resize_v && y < window_border.top) + return GTK_WINDOW_REGION_EDGE_N; + else + return GTK_WINDOW_REGION_EDGE; + break; + + case GTK_WINDOW_REGION_EDGE_NE: + if (resize_h && resize_v) + return region; + else if (resize_h && x > gtk_widget_get_allocated_width (widget) - window_border.right) + return GTK_WINDOW_REGION_EDGE_E; + else if (resize_v && y < window_border.top) + return GTK_WINDOW_REGION_EDGE_N; + else + return GTK_WINDOW_REGION_EDGE; + break; + + case GTK_WINDOW_REGION_EDGE_SW: + if (resize_h && resize_v) + return region; + else if (resize_h && x < window_border.left) + return GTK_WINDOW_REGION_EDGE_W; + else if (resize_v && y > gtk_widget_get_allocated_height (widget) - window_border.bottom) + return GTK_WINDOW_REGION_EDGE_N; + else + return GTK_WINDOW_REGION_EDGE; + break; + + case GTK_WINDOW_REGION_EDGE_SE: + if (resize_h && resize_v) + return region; + else if (resize_h && x > gtk_widget_get_allocated_width (widget) - window_border.right) + return GTK_WINDOW_REGION_EDGE_E; + else if (resize_v && y > gtk_widget_get_allocated_height (widget) - window_border.bottom) + return GTK_WINDOW_REGION_EDGE_S; + else + return GTK_WINDOW_REGION_EDGE; + break; + + default: + return region; + } +} + static gint gtk_window_button_press_event (GtkWidget *widget, GdkEventButton *event) { - GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv; + GtkWindow *window = GTK_WINDOW (widget); + GtkWindowPrivate *priv = window->priv; GdkWindowEdge edge; if (event->window == priv->grip_window) @@ -6567,26 +6768,50 @@ gtk_window_button_press_event (GtkWidget *widget, priv->title_box != NULL && !priv->fullscreen) { - GtkAllocation allocation; - int border_width; + gint x = event->x; + gint y = event->y; + GtkWindowRegion region = get_active_region_type (window, x, y); - gtk_widget_get_allocation (priv->title_box, &allocation); - border_width = - gtk_container_get_border_width (GTK_CONTAINER (priv->title_box)); + if (event->type == GDK_BUTTON_PRESS) + { + if (event->button == GDK_BUTTON_PRIMARY) + { + switch (region) + { + case GTK_WINDOW_REGION_TITLE: + case GTK_WINDOW_REGION_CONTENT: + case GTK_WINDOW_REGION_EDGE: + gdk_window_begin_move_drag_for_device (gtk_widget_get_window (widget), + gdk_event_get_device ((GdkEvent *) event), + event->button, + event->x_root, + event->y_root, + event->time); + break; + + default: + gdk_window_begin_resize_drag_for_device (gtk_widget_get_window (widget), + (GdkWindowEdge)region, + gdk_event_get_device ((GdkEvent *) event), + + event->button, + event->x_root, + event->y_root, + event->time); + break; + } - if (allocation.x - border_width <= event->x && - event->x < allocation.x + border_width + allocation.width && - allocation.y - border_width <= event->y && - event->y < allocation.y + border_width + allocation.height) + return TRUE; + } + } + else if (event->type == GDK_2BUTTON_PRESS) { - gdk_window_begin_move_drag_for_device (gtk_widget_get_window(widget), - gdk_event_get_device((GdkEvent *) event), - event->button, - event->x_root, - event->y_root, - event->time); + if (region == GTK_WINDOW_REGION_TITLE) + { + gtk_window_title_max_clicked (widget, widget); - return TRUE; + return TRUE; + } } } @@ -6609,6 +6834,20 @@ static gint gtk_window_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event) { + GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv; + + if (priv->client_decorated) + { + gint x, y; + GdkWindow *gdk_window; + + gdk_window = gtk_widget_get_window (widget); + gdk_window_get_device_position (gdk_window, + gdk_event_get_device ((GdkEvent*)event), + &x, &y, NULL); + update_cursor_at_position (widget, x, y); + } + return FALSE; } @@ -6619,6 +6858,27 @@ gtk_window_leave_notify_event (GtkWidget *widget, return FALSE; } +static gboolean +gtk_window_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event) +{ + GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv; + + if (priv->client_decorated) + { + gint x, y; + GdkWindow *gdk_window; + + gdk_window = gtk_widget_get_window (widget); + gdk_window_get_device_position (gdk_window, + gdk_event_get_device ((GdkEvent*)event), + &x, &y, NULL); + update_cursor_at_position (widget, x, y); + } + + return FALSE; +} + static void do_focus_change (GtkWidget *widget, gboolean in) @@ -6950,6 +7210,98 @@ gtk_window_real_set_focus (GtkWindow *window, } } +static void +window_cursor_changed (GdkWindow *window, + GParamSpec *pspec, + GtkWidget *widget) +{ + GTK_WINDOW (widget)->priv->default_cursor = gdk_window_get_cursor (window); +} + +static void +update_cursor_at_position (GtkWidget *widget, gint x, gint y) +{ + GtkWindow *window = GTK_WINDOW (widget); + GtkWindowPrivate *priv = window->priv; + GtkWindowRegion region; + GdkCursorType cursor_type; + GdkCursor *cursor; + GdkWindowState state; + gboolean use_default = FALSE; + GdkWindow *gdk_window; + + region = get_active_region_type (window, x, y); + + state = gdk_window_get_state (gtk_widget_get_window (widget)); + + if ((state & GDK_WINDOW_STATE_MAXIMIZED) || !priv->resizable || !priv->decorated) + { + use_default = TRUE; + } + else + { + switch (region) + { + case GTK_WINDOW_REGION_EDGE_NW: + cursor_type = GDK_TOP_LEFT_CORNER; + break; + + case GTK_WINDOW_REGION_EDGE_N: + cursor_type = GDK_TOP_SIDE; + break; + + case GTK_WINDOW_REGION_EDGE_NE: + cursor_type = GDK_TOP_RIGHT_CORNER; + break; + + case GTK_WINDOW_REGION_EDGE_W: + cursor_type = GDK_LEFT_SIDE; + break; + + case GTK_WINDOW_REGION_EDGE_E: + cursor_type = GDK_RIGHT_SIDE; + break; + + case GTK_WINDOW_REGION_EDGE_SW: + cursor_type = GDK_BOTTOM_LEFT_CORNER; + break; + + case GTK_WINDOW_REGION_EDGE_S: + cursor_type = GDK_BOTTOM_SIDE; + break; + + case GTK_WINDOW_REGION_EDGE_SE: + cursor_type = GDK_BOTTOM_RIGHT_CORNER; + break; + + default: + use_default = TRUE; + break; + } + } + + gdk_window = gtk_widget_get_window (widget); + g_signal_handlers_disconnect_by_func (G_OBJECT (gdk_window), + G_CALLBACK (window_cursor_changed), + widget); + + if (use_default) + { + gdk_window_set_cursor (gdk_window, priv->default_cursor); + } + else + { + cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), cursor_type); + gdk_window_set_cursor (gdk_window, cursor); + g_object_unref (cursor); + } + + g_signal_connect (G_OBJECT (gdk_window), "notify::cursor", + G_CALLBACK (window_cursor_changed), widget); +} + + + static void gtk_window_get_preferred_width (GtkWidget *widget, gint *minimum_size, -- 2.30.2